package com.zwh.se.collection.list;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * @author zwh
 * ArrayList类，基于数组算法的列表，通过查看源代码会发现底层其实就是一个Object数组
 * ArrayList: List 接口的大小可变数组的实现，适合给定索引查询的场景。
 * 特点：
 * 1、 可变数组:数组长度可以发生变化
 * 2、 允许包括 null 在内的所有元素。
 * 3、 除了实现 List 接口外，此类还提供一些方法来操作内部用来存储列表的数组的大小。
 * 4、 此类大致上等同于 Vector 类，除了此类是不同步的。(也就是此实现不是同步的)
 */
public class ArrayListDemo {
    public static void main(String[] args) {
        List list = new ArrayList<>();

        System.out.println("--------------------[1]新增操作--------------------");
        //boolean add(E e):将指定的元素追加到list的末尾(在ArrayList的情况下必然是true,否则只会直接抛出异常)
        boolean ifSuccess = list.add("apple");
        System.out.println("新增是否成功:" + ifSuccess);
        list.add(34);
        System.out.println("目前集合中包含的元素:" + list);

        //void add(int index, E element)。将指定的元素插入此list中的指定位置。
        list.add(1, "banana");
        System.out.println(list);

        //boolean addAll(Collection<? extends E> c):按照指定 collection 的迭代器所返回的元素顺序，将该 collection 中的所有元素添加到此list的尾部。
        list.addAll(Arrays.asList("cat", 3.8, true, "cat"));
        System.out.println(list);

        //boolean addAll(int index, Collection<? extends E> c):从指定索引位置开始插入给定 collection 中的所有元素
        list.addAll(list.size() - 2, Arrays.asList("dog", false, "dog"));
        System.out.println(list);

        System.out.println("--------------------[2]查询操作--------------------");
        //E get(int index):返回此列表中指定位置上的元素。
        System.out.println(list.get(0));

        //int indexOf(Object o):返回此列表中首次出现的指定元素的索引,如果此列表不包含元素,则返回 -1(从左往右索引查找)
        System.out.println(list.indexOf("banana"));
        System.out.println(list.indexOf("error"));

        //int lastIndexOf(Object o):返回此列表中最后一次出现的指定元素的索引如果此列表不包含元素,则返回 -1(从右往左索引查找)
        System.out.println(list.lastIndexOf("cat"));

        //boolean isEmpty():如果此列表中没有元素，则返回 true(逻辑是判断size == 0)
        System.out.println("集合是否为空：" + list.isEmpty());

        //boolean contains(Object o):如果此列表中包含指定的元素,则返回 true,否则返回false。
        System.out.println(list.contains("banana"));

        System.out.println("--------------------[3]修改操作--------------------");
        //E set(int index, E element):用指定的element替换并返回指定index上的元素(也就是被替换的元素)
        Object ele = list.set(0, "COCO");
        System.out.println(ele);
        //特别用法:交换两个索引位置上的数
        list.set(1, list.set(0, list.get(1)));
        System.out.println(list);

        System.out.println("--------------------[4]移除操作--------------------");
        //E remove(int index):删除此列表中指定索引位置上的元素并返回。
        Object removeEle = list.remove(0);
        System.out.println(removeEle);

        //boolean remove(Object o):移除此列表中首次出现的指定元素(如果存在),只删一个,找到删除后就结束了。返回值为是否删除成功
        System.out.println("集合中数据是否被删除：" + list.remove("dog"));
        System.out.println("集合中数据是否被删除：" + list.remove("coco"));

        //boolean removeAll(Collection<?> c):从该列表中移除给定列表中的所有元素(符合条件就删除,一个不留)。返回值为是否删除成功
        System.out.println(list.removeAll(Arrays.asList("cat")));
        System.out.println(list.removeAll(Arrays.asList("bear")));
        System.out.println(list);

        System.out.println("--------------------[5]遍历操作--------------------");
        //方式 1>使用普通for循环来遍历
        for (int i = 0; i < list.size(); i++) {
            Object o = list.get(i);
            System.out.print(o + " ");
        }
        System.out.println();

        //方式 2>实现类实现了Iterable接口,因此该集合支持使用foreach(其实就是迭代器的简写方式,底层用的就是迭代器,可以写是因为编译器的优化)
        for (Object o : list) {
            System.out.print(o + " ");
        }
        System.out.println();

        //方式 3>原生迭代器(国内用得比较多)
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object item = iterator.next();
            System.out.print(item + " ");
        }
        System.out.println();

        //方式 4>对迭代器的优化(国外用得比较多,同时也更节省内存)
        for (Iterator iterator2 = list.iterator(); iterator2.hasNext(); ) {
            Object item = iterator2.next();
            System.out.print(item + " ");
        }
        System.out.println();

        System.out.println("--------------------[6]其它操作--------------------");
        //int size():返回当前list中所包含的元素个数(注意返回的并非是底层的数组容量),如果该值大于 Integer.MAX_VALUE ,则返回 Integer.MAX_VALUE
        System.out.println("集合中元素的数量为：" + list.size());

        //ArrayList的toString调用的是AbstractCollection下的toString,这个类会帮你将元素迭代一遍后再输出出来
        //PS:这里跳转会去到Object是因为这里使用List接口接收的,List接口本身未与AbstractCollection有联系
        System.out.println(list.toString());

        //Object[] toArray():把列表对象转换为Object数组(按适当顺序(从第一个到最后一个元素)返回包含list中所有元素的数组。)
        Object[] objArr = list.toArray();
        //转换前有list有多少元素,length就是多少
        System.out.println(objArr.length);

        //通过传入一个list集合作为实参创建另外一个list集合
        List anotherList = new ArrayList<>(list);
        //它们属于两个不同的集合,所以地址是不一样的
        System.out.println(anotherList == list);
        //boolean equals(Object o):比较两个集合的内容并且顺序是否相同
        System.out.println(anotherList.equals(list));
        anotherList.set(0, list.get(1));
        anotherList.set(1, list.get(0));
        //虽然内容一样,但内容顺序不一样
        System.out.println(anotherList.equals(list));

        //void clear():清空list内的所有元素
        list.clear();

        System.out.println("--------------------[0]可变数组的本质--------------------");
        // 无参构造时,实际上初始化赋值的是一个空数组:this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        // ArrayList的重要属性：
        // Object[] elementData  底层存储的数组
        // size   集合内包含的元素个数
        // modCount  该集合在在结构上被修改的次数。结构修改是指那些会改变列表大小的修改,或者以其他方式扰乱它,使得正在进行的迭代可能产生不正确的结果。
        list = new ArrayList();
        System.out.println(getArrayListCapacity(list));//0
        // 当真正对数组进行添加元素操作时，才真正分配容量。
        //add方法底层:
        //首先调用一个叫做ensureCapacityInternal(int minCapacity)的方法,传入 size + 1 参数
        //if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) 判断现在底层数组是不是空数组
        // 如果是,则分配 minCapacity = Math.max(DEFAULT_CAPACITY, size + 1)
        // private static final int DEFAULT_CAPACITY = 10;默认
        //   也就是如果是空数组,则对比存入元素与默认容量(10)那个大,从而可以根据这点来进行底层数组容量的初始化
        // 然后调用ensureExplicitCapacity(minCapacity)，然后让modCount++（集合在结构上被修改次数+1）
        // if (minCapacity - elementData.length > 0)  grow(minCapacity)  如果所需的最小容量已经超过了当前底层数组的长度,则扩容
        //TIPS:集合有一个特点：只能存放引用数据类型的数据，而不能是基本数据类型(实际上是因为负责存储的数组被设计成了Object[],这是为了通用性),我们看着录入的基本类型实际上是会被自动装箱的
        list.add(1);
        System.out.println(getArrayListCapacity(list));//默认初始化容量为10
        list.clear();//这里只是看容量,为了不影响下面的说明,把里面清空一下

        //可以使用有参构造器自定义初始化容量
        ArrayList list2 = new ArrayList(2);
        System.out.println(getArrayListCapacity(list2));//2
        list2.add("java");
        list2.add("java");
        list2.add("Python");
        //todo 扩容机制:当所添加的元素数量超过所定义的list的最大容量则会触发
        // oldCapacity = (elementData.length)旧数组长度
        // newCapacity = 旧数组长度 + (旧数组长度 >> 1)。算出结果就是旧数组的1.5倍,且向下取整(本质是移位,这里是普通人的理解)。
        //   (由于ArrayList本质上是数组,所以实际的扩容其实就是新建一个数组,再把之前的元素丢到这个新数组里罢了)
        // if (newCapacity - minCapacity < 0)  newCapacity = minCapacity;
        // 如果一次性加的量甚至超过了刚算好的新容量,则使用那个值。(一般触发于addAll方法时）
        //  elementData = Arrays.copyOf(elementData, newCapacity);
        System.out.println(getArrayListCapacity(list2));//3

        //触发一次 if (newCapacity - minCapacity < 0)  newCapacity = minCapacity; 看看效果
        list2.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8));
        System.out.println(getArrayListCapacity(list2));//3

        // 在实际开发过程中，如果知道后续将要用多少容量，可以使用手动拓容(注意这个方法ArrayList独享)
        ((ArrayList) list).ensureCapacity(30);
        // 在实际开发过程中，如果明确知道未来我不会再向容器中添加元素时，最好使用trimToSize裁剪多余容量(注意这个方法ArrayList独享)
        ((ArrayList) list).trimToSize();
    }

    /**
     * 由于ArrayList没有获取容量的方法,因此这里采用反射的方式来获取
     */
    public static int getArrayListCapacity(List arrayList) {
        Object[] elementData = new Object[0];
        try {
            Class<ArrayList> arrayListClass = ArrayList.class;
            Field field = arrayListClass.getDeclaredField("elementData");
            field.setAccessible(true);
            elementData = (Object[]) field.get(arrayList);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return elementData.length;
    }
}
